#pragma once

#include "Vec3.h"

class Mat3
{
	Vec3  _m[3];

public:
	Mat3( const float d = float(1.0) )
	{
		_m[0].x = d;
		_m[1].y = d;
		_m[2].z = d;
	}

	Mat3( const Vec3& a, const Vec3& b, const Vec3& c )
	{
		_m[0] = a;
		_m[1] = b;
		_m[2] = c;
	}

	Mat3(float m00, float m10, float m20,
		 float m01, float m11, float m21,
		 float m02, float m12, float m22) 
	{
		_m[0] = Vec3( m00, m01, m02 );
		_m[1] = Vec3( m10, m11, m12 );
		_m[2] = Vec3( m20, m21, m22 );
	}

	Mat3( const Mat3& m )
	{
		if ( *this != m ) {
			_m[0] = m._m[0];
			_m[1] = m._m[1];
			_m[2] = m._m[2];
		} 
	}

	Mat3 transpose() const
	{
		return Mat3(
			_m[0][0], _m[1][0], _m[2][0],
			_m[0][1], _m[1][1], _m[2][1],
			_m[0][2], _m[1][2], _m[2][2]
		);
	}

	Vec3& operator [] ( int i ) { return _m[i]; }
	const Vec3& operator [] ( int i ) const { return _m[i]; }

	Mat3 operator + ( const Mat3& m ) const
	{
		return Mat3(
			_m[0]+m[0],
			_m[1]+m[1],
			_m[2]+m[2] );
	}

	Mat3 operator - ( const Mat3& m ) const
	{
		return Mat3(
			_m[0]-m[0],
			_m[1]-m[1],
			_m[2]-m[2] );
	}

	Mat3 operator * ( const float s ) const 
	{
		return Mat3(
			s*_m[0],
			s*_m[1],
			s*_m[2] );
	}

	Mat3 operator / ( const float s ) const
	{
		float r = float(1.0) / s;
		return *this * r;
	}

	friend Mat3 operator * ( const float s, const Mat3& m )
	{
		return m * s;
	}

	Mat3 operator * ( const Mat3& m ) const
	{
		Mat3  a( 0.0 );

		for ( int i = 0; i < 3; ++i ) {
			for ( int j = 0; j < 3; ++j ) {
				for ( int k = 0; k < 3; ++k ) {
					a[i][j] += _m[i][k] * m[k][j];
				}
			}
		}

		return a;
	}

	Mat3& operator += ( const Mat3& m )
	{
		_m[0] += m[0];
		_m[1] += m[1];
		_m[2] += m[2]; 
		return *this;
	}

	Mat3& operator -= ( const Mat3& m )
	{
		_m[0] -= m[0]; 
		_m[1] -= m[1];
		_m[2] -= m[2]; 
		return *this;
	}

	Mat3& operator *= ( const float s )
	{
		_m[0] *= s;  _m[1] *= s;  _m[2] *= s; 
		return *this;
	}

	Mat3& operator *= ( const Mat3& m )
	{
		Mat3  a( 0.0 );

		for ( int i = 0; i < 3; ++i ) {
			for ( int j = 0; j < 3; ++j ) {
				for ( int k = 0; k < 3; ++k ) {
					a[i][j] += _m[i][k] * m[k][j];
				}
			}
		}

		return *this = a;
	}

	Mat3& operator /= ( const float s )
	{
		float r = float(1.0) / s;
		return *this *= r;
	}

	Vec3 operator * ( const Vec3& v ) const // m * v
	{
		return Vec3(
			_m[0][0]*v.x + _m[0][1]*v.y + _m[0][2]*v.z,
			_m[1][0]*v.x + _m[1][1]*v.y + _m[1][2]*v.z,
			_m[2][0]*v.x + _m[2][1]*v.y + _m[2][2]*v.z
		);
	}

	friend std::ostream& operator << ( std::ostream& os, const Mat3& m )
	{
		return os << std::endl
			<< m[0] << std::endl
			<< m[1] << std::endl
			<< m[2] << std::endl;
	}

	friend std::istream& operator >> ( std::istream& is, Mat3& m )
	{
		return is >> m._m[0] >> m._m[1] >> m._m[2] ;
	}

	operator const float* () const
	{
		return static_cast<const float*>( &_m[0].x );
	}

	operator float* ()
	{
		return static_cast<float*>( &_m[0].x );
	}
};
